#include "CBL_Debug.h"
 
#ifdef CB_MACOSX
#include <CoreFoundation/CoreFoundation.h>
#include <execinfo.h>
#include <stdio.h>
#include "CBL_Rtti.h"
#include <cxxabi.h>
#endif

#ifdef CB_WINDOWS
#include <windows.h>
#include <dbghelp.h>
#include <stdlib.h>
#endif

#include "CBL_Locks.h"

CB_SUBNAMESPACE_START(Debug)

#ifdef CB_DEBUG

void Error(const char* _pFormat, ...)
{
	va_list _Arguments;
	va_start(_Arguments, _pFormat);
    vprintf(_pFormat, _Arguments);
	va_end(_Arguments);
}

// -------------------------------------------------------------------------------------------------

bool Verify(bool _Condition, const char* _pFormat, ...)
{
	if (!_Condition)
    {
    	char Message[MaxDebugMessageLength];
    	
		va_list _Arguments;
		va_start(_Arguments, _pFormat);
    	vsnprintf(Message, MaxDebugMessageLength, _pFormat, _Arguments);
    	va_end(_Arguments);
    
    	AssertWindow(Message);
    }

	return _Condition;
}

// -------------------------------------------------------------------------------------------------

void Assert(bool _Condition, const char* _pFormat, ...)
{
	if (!_Condition)
    {
        char Message[MaxDebugMessageLength];
        
        va_list _Arguments;
        va_start(_Arguments, _pFormat);
        vsnprintf(Message, MaxDebugMessageLength, _pFormat, _Arguments);
        va_end(_Arguments);
        
        AssertWindow(Message);
    }
}

#endif

// ---------------------------------------------------------------------------------------------------------------------
//	MacOSX
// ---------------------------------------------------------------------------------------------------------------------

#ifdef CB_MACOSX

void AssertWindow(const char* _pMessage)
{
	static Cpu::CMutex CallStackMutex;
	CB_CRITICAL_SECTION_GUARD(CallStackMutex);

    // Get the callstack
    
    void* pCallStack[MaxCallStackItems];
    char CallStackStr[MaxCallstackLineLength * MaxCallStackItems];
    char CallStackLineStr[MaxCallstackLineLength];
    char FullMessage[MaxDebugMessageLength + 20 + sizeof(CallStackStr)];
    
    uint32 Offset = 0;
    
    int StackNumber;
    int Address;
    int InternalOffset;
    char Function[MaxCallstackLineLength];
    char Executeable[MaxCallstackLineLength];
    
    int Frames 			  = backtrace(pCallStack, MaxCallStackItems);
    char** ppTraceStrings = backtrace_symbols(pCallStack, Frames);
	
	for (int Frame(0); (Frame<Frames) && (Offset < sizeof(CallStackStr)); ++Frame)
    {
    	// demangle the string, so it's more readable inside the message window
    
    	sscanf(ppTraceStrings[Frame], "%d%s%i%s + %d", &StackNumber, Executeable, &Address, Function, &InternalOffset);
    
    	snprintf(CallStackLineStr, MaxCallstackLineLength - Offset, 
				 "%s [0x%x] + %d\n> %s\n\n", 
				 Executeable, 
				 Address, InternalOffset, 
				 RTTI::Demangle(Function));
    	
    	strncpy(&CallStackStr[Offset], CallStackLineStr, sizeof(CallStackStr) - Offset);
    
    	Offset += strlen(CallStackLineStr);
    }
    
    free(ppTraceStrings);
    
    // Merge message and callstack
    
    snprintf(FullMessage, sizeof(FullMessage), "%s\n\nCallstack:\n%s", _pMessage, CallStackStr);
    
    // Now for the message box
    
    CFOptionFlags Action;
    
    CFStringRef Title 		  = CFStringCreateWithCString(NULL, "Debug error", kCFStringEncodingUTF8);
    CFStringRef Message		  = CFStringCreateWithCString(NULL, FullMessage, kCFStringEncodingUTF8);
    CFStringRef LabelBreak    = CFStringCreateWithCString(NULL, "Debug", kCFStringEncodingUTF8);
    CFStringRef LabelContinue = CFStringCreateWithCString(NULL, "Ignore", kCFStringEncodingUTF8);
    CFStringRef LabelExit     = CFStringCreateWithCString(NULL, "Kill", kCFStringEncodingUTF8);
    
    CFUserNotificationDisplayAlert(0, kCFUserNotificationPlainAlertLevel, NULL, NULL, NULL, Title, Message, LabelBreak, LabelContinue, LabelExit, &Action);
    
    switch (Action)
    {
    case kCFUserNotificationDefaultResponse:
        printf("Breakpoint by user.\n");
        CB_ASM 
        {
        	ASM_BREAKPOINT;
        }
        break;
        
    case kCFUserNotificationOtherResponse:
        printf("Exit by user.\n");
		CallStackMutex.Unlock();
        exit(0);
        break;
    
    default:
    case kCFUserNotificationCancelResponse:
    case kCFUserNotificationAlternateResponse:
        break;
    }
}

#endif

// ---------------------------------------------------------------------------------------------------------------------
//	Win32
// ---------------------------------------------------------------------------------------------------------------------

#ifdef CB_WINDOWS

BOOL __stdcall ReadProcessMemoryEx(HANDLE _pProcess, DWORD _BaseAddress, PVOID _pBuffer, DWORD _Size, LPDWORD _pNumberOfBytesRead)
{
    SIZE_T BytesRead;
    BOOL ReturnValue = ReadProcessMemory(_pProcess, (LPVOID) _BaseAddress, _pBuffer, _Size, &BytesRead);
    *_pNumberOfBytesRead = (DWORD)BytesRead;
    return ReturnValue;
}

void AssertWindow(const char* _pMessage)
{
	static Cpu::CMutex CallStackMutex;
	CB_CRITICAL_SECTION_GUARD(CallStackMutex);

	// Get the callstack

	char CallStackStr[MaxCallstackLineLength * MaxCallStackItems];
	char CallStackLineStr[MaxCallstackLineLength];
	char FullMessage[MaxDebugMessageLength + 20 + sizeof(CallStackStr)];

	uint32 CallStackOffset = 0;

	STACKFRAME NextStackFrame;
	CONTEXT ApplicationContext;
	SYMBOL_INFO* pSymbol;
	DWORD64 SymbolOffset;
	DWORD LineOffset;
	IMAGEHLP_LINE Line;
	IMAGEHLP_MODULE Module;

	char SymbolInfoBuffer[sizeof(SYMBOL_INFO) + MaxCallstackLineLength];
		
	HANDLE CurrentProcess = GetCurrentProcess();
	HANDLE CurrentThread  = GetCurrentThread();
	
	RtlCaptureContext(&ApplicationContext);
	
	memset(&NextStackFrame, 0, sizeof(NextStackFrame));

	NextStackFrame.AddrPC.Offset    = ApplicationContext.Eip; // Instructon pointer
	NextStackFrame.AddrStack.Offset = ApplicationContext.Esp; // Stack pointer
	NextStackFrame.AddrFrame.Offset = ApplicationContext.Ebp; // Frame pointer

	NextStackFrame.AddrPC.Mode    = AddrModeFlat;
	NextStackFrame.AddrStack.Mode = AddrModeFlat;
	NextStackFrame.AddrFrame.Mode = AddrModeFlat;

	SymInitialize(CurrentProcess, NULL, TRUE);

	pSymbol = (SYMBOL_INFO*)SymbolInfoBuffer;

	memset(SymbolInfoBuffer, 0, sizeof(SymbolInfoBuffer));
	pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO);
	pSymbol->Size         = MaxCallstackLineLength;
	pSymbol->MaxNameLen   = MaxCallstackLineLength-1;

	memset(&Module, 0, sizeof(Module));
	Module.SizeOfStruct = sizeof(Module);

	memset(&Line, 0, sizeof(Line));
	Line.SizeOfStruct = sizeof(Line);

	uint32 CallStackItems = 0;
	
	// Skips the first stack entry, as it is this function here (not relevant)
	while ((CallStackItems < MaxCallStackItems) && 
			StackWalk(IMAGE_FILE_MACHINE_I386, CurrentProcess, CurrentThread, &NextStackFrame,
					  NULL, NULL, &SymFunctionTableAccess, &SymGetModuleBase, NULL))
	{
		SymFromAddr(CurrentProcess, NextStackFrame.AddrPC.Offset, &SymbolOffset, pSymbol);
		SymGetLineFromAddr(CurrentProcess, NextStackFrame.AddrPC.Offset, &LineOffset, &Line);
		SymGetModuleInfo(CurrentProcess, NextStackFrame.AddrPC.Offset, &Module);

		sprintf(CallStackLineStr, 
				"%s [0x%x] + %d\n> %s(), %s (line %d)\n\n",
				strrchr((char*)Module.LoadedImageName, CB_DIRECTORY_SEPARATOR) + 1,
				(int)pSymbol->Address, (int)SymbolOffset, 
				(char*)pSymbol->Name, 
				strrchr((char*)Line.FileName, CB_DIRECTORY_SEPARATOR) + 1, 
				(int)Line.LineNumber);

		if (CallStackOffset + strlen(CallStackLineStr) > sizeof(CallStackStr))
			break;
    
		strncpy(&CallStackStr[CallStackOffset], CallStackLineStr, sizeof(CallStackStr) - CallStackOffset);
    
    	CallStackOffset += strlen(CallStackLineStr);
		++CallStackItems;
	}

    // Merge message and callstack
    
    sprintf(FullMessage, "%s\n\nCallstack:\n%s", _pMessage, CallStackStr);
    
    // Now for the message box
    
    int Result = MessageBox(NULL, FullMessage, "Debug Error", MB_ABORTRETRYIGNORE | MB_ICONEXCLAMATION | MB_DEFBUTTON3 | MB_APPLMODAL);
    
    switch (Result)
    {
    case IDRETRY:
        printf("Breakpoint by user.\n");
        CB_ASM 
        {
        	ASM_BREAKPOINT;
        }
        break;
        
    case IDABORT:
        printf("Exit by user.\n");
		CallStackMutex.Unlock();
        exit(0);
        break;
    
    default:
    case IDIGNORE:
        break;
    }
}

#endif

CB_SUBNAMESPACE_END